﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Web;
using System.Net;
using System.Xml;
using System.Globalization;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

/// --------------------------------------------------------------------------------------------------------------------
/// <copyright file="Session.cs" company="Nozbe.Net">
/// Copyright (c) 2011 Michael W. Koehler
/// </copyright>
/// <author>Michael W. Koehler</author>
/// <license>
/// Permission is hereby granted, free of charge, to any person obtaining a copy
/// of this software and associated documentation files (the "Software"), to deal
/// in the Software without restriction, including without limitation the rights
/// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
/// copies of the Software, and to permit persons to whom the Software is 
/// furnished to do so, subject to the following conditions:
/// 
/// The above copyright notice and this permission notice shall be included in all
/// copies or substantial portions of the Software.
/// 
/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
/// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
/// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
/// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 
/// SOFTWARE. 
/// </license>
/// <website>http://nozbenet.codeplex.com</website>
/// <summary>  
/// <para>
/// Nozbe.Net is a .Net library that provides easy programmed access to Nozbe. Developed in C#, 
/// it can either jumpstart a new Nozbe app, or allow Powershell scripts access to Nozbe.
/// </para>
/// </summary>
/// <remarks>
/// <para>
/// Nozbe.Net accesses Nozbe via it's Sync2 web service.  It's goal is to offer easy access to Nozbe's web service api
/// to programs written in .Net languages or to Powershell scripts.  It does this by translating the web service calls and json data
/// offered by Nozbe, into .Net methods and .Net collections.
/// </para>
/// <para>
/// At this time, Nozbe.net is mostly a thin wrapper over the Nozbe api.  It is my intention to extend it over time as common use cases
/// argue for extending it.  For example, the SaveToDisk and BackupToDisk methods are common use cases that seemed best offered by the library, rather than
/// require each program or script to implement it's own.  Of course, the more basic methods do allow implementing variants as needed or desired.
/// </para>
/// <para>
/// Nozbe.Net is currently read-only.  The process verb is not fully implemented as I am still working through how best to present it.  I am making
/// this project public now to gather feedback and suggestions on this and other design points.
/// </para>
/// <para>
/// Access to Nozbe requires that you receive an application key from Nozbe, and that you use it to authenticate along with the user's key.
/// Full documentation of the Nozbe web service API and an application key can be requested from:
/// </para>
/// <list type="table">  
/// <listheader><person>Contact</person><email>Email</email><comment>Comment</comment></listheader>  
/// <item><person>Tom Kapelak</person><email>tom@nozbe.com</email><comment>Nozbe CTO - contact him for technical questions.</comment></item>  
/// <item><person>Michael Sliwinski</person><email>michael@nozbe.com</email><comment>Nozbe CEO - contact him for business development questions.</comment></item>  
/// </list>  
/// </remarks>
/// --------------------------------------------------------------------------------------------------------------------

namespace Nozbe.Net
{
    /// <summary>
    /// <para>
    /// The Session class represents a combination of an application key and a user key, which together authenticate the calling application to Nozbe,
    /// along with
    /// </para>
    /// </summary>
    /// <remarks>
    /// <para>
    /// An actual "session" or "connection" is not really created as Nozbe provides a web service interface which is stateless. 
    /// A session object is simply a handy encapsulation of the methods and data needed to construct web service calls to Nozbe.
    /// </para>
    /// </remarks>
    public class Session
    {
        public string prodBaseURL { get { return "http://www.nozbe.com/sync2/"; } }
        public string devBaseURL { get { return "http://devl.nozbe.net/sync2/"; } }
        private string _baseURL;
        public string baseURL { get { return _baseURL;} }
       
        public string appKey { get; set; }
        public string userKey { get; set; }

        private System.Collections.ArrayList _lastErrors;
        public System.Collections.ArrayList lastErrors { get { return _lastErrors; } }

        private string _lastResult;
        public string lastResult { get { return _lastResult; } }

        private TimeSpan tsOffset;

        /// <summary>
        /// Construct a new Session object.
        /// </summary>
        /// <param name="aKey">
        /// The unique key assigned by Nozbe to the application.  Contact Nozbe to get an application key.
        /// </param>
        /// <param name="uKey">
        /// The unique key assigned to the Nozbe user, whose account will be accessed.  The user can either provide their key directly, or
        /// provide their logon and password which you can use to call Session::Login() [see below].
        /// </param>
        /// <param name="development">
        /// If false, the production Nozbe server will be called.  If true, the development Nozbe server will be used.
        /// See SetDevServer() and SetProdServer()
        /// </param>
        public Session(string aKey, string uKey, bool development)
        {
            Init(aKey, uKey, development);
        }

        public Session(string aKey, string uKey)
        {
            Init(aKey, uKey, false);
        }

        public Session(string aKey)
        {
            Init(aKey, "", false);
        }

        public Session()
        {
            Init("", "", false);            
        }

        private void Init(string aKey, string uKey, bool development) 
        {
            appKey = aKey;
            userKey = uKey;
            _lastErrors = new System.Collections.ArrayList();
            if (development)
            {
                SetDevServer();
            }
            else
            {
                SetProdServer();
            }

            if (aKey != "" && uKey != "")
            {
                UpdateTSOffset();
            }
            else
            {
                tsOffset = new TimeSpan(0);
            }
        }

        /// <summary>
        /// Login to Nozbe with user id and password.
        /// </summary>
        /// <param name="userID">
        /// The Nozbe user id.
        /// </param>
        /// <param name="password">
        /// The Nozbe password.
        /// </param>
        /// <returns>
        /// <para>
        /// True (logon succeeded) or False (logon failed).  If the logon succeeded, the Session object retains the user key that was returned and 
        /// uses it in subsequent calls.  If the logon failed, the error(s) is stored in the lastErrors property.
        /// </para>
        /// <para>
        /// Since calls to Nozbe are stateless, there is no Logoff.
        /// </para>
        /// </returns>    
        public bool Login(string userID, string password)
        {
            string url = GetNozbeURL("login");

            string pwdHash = GetMD5Hash(password);
            url += "/email-" + HttpUtility.UrlEncode(userID) + "/password-" + pwdHash;
            string result = GetURL(url);
            var o = JObject.Parse(result);

            if (!IsNozbeError(result, o))
            {
                // result should be of the form {"key":"123456789abcdef"}                
                string key = (string)o["key"];
                if (key == null || key == "null" || key == "")
                {
                    userKey = "";
                    return false;
                }
                else
                {
                    userKey = key;
                    UpdateTSOffset();
                    return true;
                }                
            }
            else
            {
                userKey = "";
                return false;
            }
        }

        /// <summary>
        /// Sign up for a free Nozbe account.  Upgrading to a paid account can be done from the web site.
        /// </summary>
        /// <param name="name">
        /// The name the user wishes to use on Nozbe.
        /// </param>
        /// <param name="email">
        /// The user's email address.
        /// </param>
        /// <param name="password">
        /// The initial password.  Must be at least 5 characters.
        /// </param>
        /// <returns>
        /// <para>
        /// True (SignUp succeeded) or False (SignUp failed).  If the SignUp succeeded, the Session object retains the user key that was returned and 
        /// uses it in subsequent calls.  If the SignUp failed, the error(s) is stored in the lastErrors property.
        /// </para>
        /// </returns>    
        public bool SignUp(string name, string email, string password)
        {
            string url = GetNozbeURL("signup");

            string pwdHash = GetMD5Hash(password);
            url += "/name-" + HttpUtility.UrlEncode(name) + "/email-" + HttpUtility.UrlEncode(email) + "/password-" + HttpUtility.UrlEncode(password);
            string result = GetURL(url);
            var o = JObject.Parse(result);

            if (!IsNozbeError(result, o))
            {
                // result should be of the form {"key":"123456789abcdef"}                
                string key = (string)o["key"];
                if (key == null || key == "null" || key == "")
                {
                    userKey = "";
                    return false;
                }
                else
                {
                    userKey = key;
                    UpdateTSOffset();
                    return true;
                }
            }
            else
            {
                userKey = "";
                return false;
            }            
        }

        /// <summary>
        /// Nozbe's Sync2 api syncronizes based on time stamp.  For this to work, all applications must use time in sync with the Nozbe server.
        /// To ensure this is the case, this method compares current local time to the current Nozbe server time and calculates the time offset.
        /// </summary>
        /// <returns>
        /// <para>
        /// True if the time offset was updated.  False if the current Nozbe time could not be retrieved.
        /// </para>
        /// <para>
        /// The current offset is stored within the Session object for use in subsequent calls.
        /// </para>
        /// </returns>
        public bool UpdateTSOffset()
        {
            DateTime now = DateTime.Now;
            var ts = GetServerTS();
            if (ts != "")
            {
                DateTime serverTime = DateTime.Parse(ts);
                tsOffset = serverTime - now;
                return true;
            }
            else
            {
                return false;
            }
        }

        /// <summary>
        /// Returns the current timestamp values for each or a specific Nozbe data set.
        /// </summary>
        /// <param name="what">
        /// Selects the “ts” for: project, context, task, note, or upload.  "" selects the default which returns all timestamps.
        /// </param>
        /// <param name="dev">
        /// The number of the client device.  If 0, nothing is specified to Nozbe, which will default to 1.
        /// </param>
        /// <returns>
        /// A hashtable with the time stamp information for each returned section.
        /// </returns>
        public System.Collections.Hashtable GetTS()
        {
            return GetTS("", 0);
        }
        public System.Collections.Hashtable GetTS(string what)
        {
            return GetTS(what, 0);
        }
        public System.Collections.Hashtable GetTS(string what, int dev)
        {
            string url = GetNozbeURL("getts");

            if (what.Length > 0)
            {
                url += "/what-" + what;
            }
            if (dev > 0)
            {
                url += "/dev-" + dev.ToString();
            }

            string result = GetURL(url);
            var o = JObject.Parse(result);

            if (!IsNozbeError(result, o))
            {
                var hash = new System.Collections.Hashtable();

                var e = o.GetEnumerator();
                do
                {
                    var kv = e.Current;
                    if (kv.Key != null && kv.Value != null)
                    {
                        JToken token = kv.Value;
                        string v = token.ToString();
                        if (v[0] == '"')
                        {
                            v = v.Substring(1, (v.Length - 2));
                        }
                        hash.Add(kv.Key, v);
                    }
                } while (e.MoveNext());

                return hash;
            }
            else
            {                
                return null;
            }
        }

        /// <summary>
        /// Gets current time stamp from the Nozbe server.
        /// </summary>
        /// <returns>
        /// The time stamp in string format.
        /// </returns>
        public string GetServerTS()
        {
            string url = GetNozbeURL("getserver_ts");

            string result = GetURL(url);        //{"ts":"2009-09-30 12:56:46"}
            var o = JObject.Parse(result);

            if (!IsNozbeError(result, o))
            {
                return (string)o["ts"];
            }
            else
            {
                return "";
            }
        }

        /// <summary>
        /// Gets the user's data from the Nozbe server.
        /// </summary>
        /// <param name="what">
        /// Selects the “ts” for: project, context, task, note, or upload.  "" selects the default which returns data from all sections.
        /// </param>
        /// <param name="showDone">
        /// True returns completed tasks, projects, etc.  False only includes incomplete items. 
        /// </param>
        /// <param name="page">
        /// set as 1, 2, 3 etc. if you want to get only subset of notes (one page contains max. 20 notes).  0 means all at once.
        /// </param>
        /// <returns>
        /// Returns a hash table, where each key is a section like project, context, task, note, etc.  Most of these are arrays, but userinfo is a hash also.
        /// </returns>
        public System.Collections.Hashtable GetData()
        {
            return GetData("", false, 0);
        }
        public System.Collections.Hashtable GetData(string what)
        {
            return GetData(what, false, 0);
        }
        public System.Collections.Hashtable GetData(string what, bool showDone)
        {
            return GetData(what, showDone, 0);
        }
        public System.Collections.Hashtable GetData(string what, bool showDone, int page)
        {
            string url = GetNozbeURL("getdata");

            if (what.Length > 0)
            {
                url += "/what-" + what;
            }
            if (showDone)
            {
                url += "/showdone-1";
            }
            if (page > 0)
            {
                url += "/page-" + page.ToString();
            }

            string result = GetURL(url);
            var o = JObject.Parse(result);

            if (o.Type == JTokenType.Object && !IsNozbeError(result, o))
            {
                return GetJSONObject(o);              
            }
            else
            {
                return null;
            }
        }

        /// <summary>
        /// "Process" or synchronize changes made locally with changes made elsewhere, updating the Nozbe server and our local data store.
        /// 
        /// This method is not completed, and currently returns null.
        /// </summary>
        /// <param name="ts">
        /// 
        /// </param>
        /// <param name="data">
        /// 
        /// </param>
        /// <returns>
        /// 
        /// </returns>
        public System.Collections.Hashtable Process(string ts, System.Collections.Hashtable data)
        {
            return null; // This method is not complete, so we do nothing (yet)

            //string url = GetNozbeURL("process");

            //int id = 0;

            //if (ts.Length > 0)
            //{
            //    url += "/ts-" + ts;
            //}

            //var changes = new System.Collections.Hashtable();
            //foreach (string key in data.Keys)
            //{
            //    // The data hashtable is a set of key/value pairs, one for each of project, task, tag, context, etc
            //    //
            //    // Most of them are arrays (some are just objects).  For each, check if any have ts != _ts_original and if so add to changes
            //    //
            //    Object item = data[key];
            //    if (item != null) 
            //    {
            //        if (item is System.Collections.ArrayList)
            //        {
            //            var arr = (System.Collections.ArrayList) item;
            //            for (int i = 0; i < arr.Count; i++)
            //            {
            //                bool changed = false;

            //                var hash = (System.Collections.Hashtable)arr[i];
            //                if (hash.ContainsKey("ts"))
            //                {
            //                    if (hash.ContainsKey("_ts_orignal"))
            //                    {
            //                        if (hash["ts"] != hash["_ts_original"])
            //                        {
            //                            changed = true;
            //                        }
            //                    }
            //                    else
            //                    {
            //                        // Not sure how there is a ts but not a _ts_original, so place safe and assume changed
            //                        changed = true;
            //                    }
            //                }
            //                else
            //                {
            //                    // No ts, so assume this is a new thing.  Assign a unique id so we can relate the assigned ts to this record
            //                    id++;
            //                    if (hash.ContainsKey("id"))
            //                    {
            //                        hash["id"] = id.ToString();
            //                    }
            //                    else
            //                    {
            //                        hash.Add("id", id.ToString());
            //                    }
            //                }

            //                if (changed)
            //                {
            //                    if (!changes.ContainsKey(key))
            //                    {
            //                        changes.Add(key, new System.Collections.ArrayList());
            //                    }

            //                    var newArr = (System.Collections.ArrayList) changes[key];
            //                    newArr.Add(hash);
            //                }
            //            }
            //        }
            //        //else if (item is System.Collections.Hashtable)
            //        //{
            //        //    // This only applies to "userinfo" and possibly "team" but team should be an arraylist
            //        //    // Since userinfo does not have a ts, I see it as information to us that we cannot change
            //        //}
            //    }
            //}
            
            
            //// Convert changes to json and submit
            //var jObj = Newtonsoft.Json.Linq.JObject.FromObject(changes);
            //string json = jObj.ToString();
            //Console.WriteLine("Changes: " + json);
            //string result = PostURL(url, json);            
            //var o2 = JObject.Parse(result);

            //if (o2.Type == JTokenType.Object && !IsNozbeError(result, o2))
            //{
            //    //
            //    // !! Process results by updating data and return the update data collection
            //    //
            //    var newData = new System.Collections.Hashtable();
            //    return newData;
            //}
            //else
            //{
            //    // !!
            //    return null;
            //}
        }

        /// <summary>
        /// Returns the Nozbe url to use given a Nozbe web service method and the app key and user key.
        /// 
        /// This is a utility method used internally, but made public in case the web server api is updated and programs needed to bypass or extend
        /// the methods in this library.
        /// </summary>
        /// <param name="method">
        /// The Nozbe web service method to use: login, getdata, process, etc.
        /// </param>
        /// <returns>
        /// The URL to use to invoke the web service method.
        /// </returns>
        public string GetNozbeURL(string method)
        {
            string url = baseURL + method + "/app_key-" + appKey;

            if (userKey != "")
            {
                url += "/key-" + userKey;
            }

            return url;
        }

        /// <summary>
        /// Normally an application will request services from the production Nozbe server, but during software development and testing, the
        /// development server should be used.
        /// 
        /// Developers can select the development server or switch back to the production server with these methods.
        /// 
        /// Note that the production and development Nozbe servers use different application keys, and different user accounts.
        /// </summary>
        /// <returns>
        /// None.  Session object state is changed to use the selected server.
        /// </returns>
        public void SetDevServer()
        {
            _baseURL = devBaseURL;            
        }

        public void SetProdServer()
        {
            _baseURL = prodBaseURL;
        }

        /// <summary>
        /// Returns a current time stamp to use for a new or modified item.
        /// </summary>
        /// <returns>
        /// The time stamp in Nozbe format as a string.
        /// </returns>
        public string GetNewTS()
        {
            var now = System.DateTime.Now;
            now = now + tsOffset;
            string newTS = now.ToString("yyyy'-'MM'-'dd' 'HH':'mm':'ss");
            return newTS;
        }

        /// <summary>
        /// Writes a data collection to a date/time stamped backup set on disk.
        /// 
        /// The data is stored in a backup folder, in a date/time named sub folder as a json file formatted to be as human readable as possibe.
        /// 
        /// Files attached to projects are saved to sub folders named for the project.
        /// </summary>
        /// <param name="data">
        /// The data in the same format as returned by GetData.
        /// </param>
        /// <param name="backupDir">
        /// The top level backup directory to save the date/time named backup set folder to.  
        /// 
        /// If not specified, or "", the default is a folder called NozbeBackups under My Documents.
        /// </param>
        /// <returns>
        /// True if the backup succeeded, False if it failed, in which case lastErrors will contain the error(s).
        /// </returns>
        public bool BackupToDisk(System.Collections.Hashtable data)
        {
            return BackupToDisk(data, "");
        }

        public bool BackupToDisk(System.Collections.Hashtable data, string backupDir)
        {
            if (backupDir == "")
            {
                backupDir = System.Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + "\\NozbeBackups";
            }

            if (!System.IO.Directory.Exists(backupDir)) 
            {
                var dir = System.IO.Directory.CreateDirectory(backupDir);
                if (!dir.Exists)
                {
                    return false;
                }
            }

            var now = System.DateTime.Now;
            string saveDir = backupDir + "\\" + now.ToString("yyyy'-'MM'-'dd' 'HH'-'mm'-'ss");

            return SaveToDisk(data, saveDir);
        }

        /// <summary>
        /// Writes a data collection to a folder.
        /// 
        /// The data is stored as a json file formatted to be as human readable as possibe.
        /// 
        /// Files attached to projects are saved to sub folders named for the project.
        /// </summary>
        /// <param name="data">
        /// The data in the same format as returned by GetData.
        /// </param>
        /// <param name="saveDir">
        /// The folder to save the data to.
        /// </param>
        /// <returns>
        /// True if the save succeeded, False if it failed, in which case lastErrors will contain the error(s).
        /// </returns>
        public bool SaveToDisk(System.Collections.Hashtable data, string saveDir)
        {
            bool noErrors = true;

            if (!System.IO.Directory.Exists(saveDir))
            {
                var dir = System.IO.Directory.CreateDirectory(saveDir);
                if (!dir.Exists)
                {
                    return false;
                }
            }

            // Save it as json (since Nozbe uses that)
            var file = saveDir + "\\Nozbe.json";
            string json = JsonConvert.SerializeObject(data, Newtonsoft.Json.Formatting.Indented);  // I chose to make it nicely formatted to make it more human readable
            using (var w = new System.IO.FileStream(file, System.IO.FileMode.Create))
            {                
                var encoding = new System.Text.UTF8Encoding();
                var b = encoding.GetBytes(json);
                w.Write(b, 0, b.Length);
                w.Close();
            }

            // Save it as XML (in case xml is easier than json for people)
            //file = currDir + "\\Nozbe.xml";           
            //XmlDocument doc = (XmlDocument)JsonConvert.DeserializeXmlNode(json);
            //doc.Save(file);

            // Pull down all the files attached to projects
            using (var client = new WebClient())
            {
                var projects = (System.Collections.ArrayList) data["project"];

                var uploads = (System.Collections.ArrayList) data["upload"];

                foreach (var fo in uploads) 
                {
                    var f = (System.Collections.Hashtable) fo;
                    var projHash = (string)f["project_hash"];

                    System.Collections.Hashtable proj = null;
                    foreach (var po in projects)
                    {
                        var p = (System.Collections.Hashtable) po;
                        var pHash = (string)p["hash"];

                        if (projHash == pHash)
                        {
                            proj = p;
                        }
                    }
                    
                    var projName = "Unknown";
                    if (proj!=null)
                    {
                        projName = (string)proj["name"];
                    }
                                   
                    string projDir = saveDir + "\\" + projName;
                    if (!System.IO.Directory.Exists(projDir)) 
                    {
                        System.IO.Directory.CreateDirectory(projDir);
                    }
                    
                    string fname = projDir + "\\" + ((string)f["name"]);

                    string url = ((string)f["url"]) + "/key-" + this.userKey;
                                
                    client.DownloadFile(url, fname);
                }
            }

            return noErrors;
        }

        private System.Collections.Hashtable ConvertFromJsonObject(string json)
        {
            var o = JObject.Parse(json);

            if (o.Type == JTokenType.Object)
            {
                return GetJSONObject(o);
            }
            else
            {
                return null;
            }
        }

        private string ConvertToJsonObject(System.Collections.Hashtable data)
        {
            string json = JsonConvert.SerializeObject(data);
            return json;
        }

        private System.Collections.Hashtable GetJSONObject(JObject o)
        {
            var hash = new System.Collections.Hashtable();
            var e = o.GetEnumerator();
            do
            {
                var kv = e.Current;
                if (kv.Key != null && kv.Value != null)
                {
                    JToken value = kv.Value;

                    Object v = GetJSONValue(value);

                    hash.Add(kv.Key, v);

                    if (kv.Key == "ts" || kv.Key == "date" || kv.Key == "datetime")
                    {
                        if (kv.Key == "ts")
                        {
                            // We save a copy of the original ts so we can check for changes during process().
                            // It is the caller's responsibility to update ts when changing existing items.
                            hash.Add("_ts_original", v);
                        }

                        string k = "_" + kv.Key + "_dt";

                        if (v != null)
                        {
                            try
                            {
                                DateTime dt = DateTime.Parse((string)v);
                                hash.Add(k, dt);
                            }
                            catch
                            {
                                hash.Add(k, v);
                            }
                        }
                        else
                        {
                            hash.Add(k, v);
                        }
                    }
                }
            } while (e.MoveNext());

            return hash;            
        }

        private System.Collections.ArrayList GetJSONArray(JArray o)
        {
            var array = new System.Collections.ArrayList();

            foreach (var child in o.Children())
            {
                Object v = GetJSONValue(child);
                
                array.Add(v);
            }

            return array;
        }

        private Object GetJSONValue(JToken token)
        {
            Object value = null;

            string s = token.ToString();
            if (s[0] == '"')
            {
                s = s.Substring(1, (s.Length - 2));
            }

            switch (token.Type)
            {
                case JTokenType.Array:
                    value = GetJSONArray((JArray)token);
                    break;
                case JTokenType.Boolean:
                    if (s == "true")
                    {
                        value = true;
                    }
                    else
                    {
                        value = false;
                    }
                    break;
                case JTokenType.Bytes:
                    value = token.ToString();
                    break;
                case JTokenType.Comment:
                    value = s;
                    break;
                case JTokenType.Constructor:
                    value = token.ToString();
                    break;
                case JTokenType.Date:
                    value = DateTime.Parse(s);
                    break;
                case JTokenType.Float:
                    value = float.Parse(s);
                    break;
                case JTokenType.Integer:
                    value = int.Parse(s);
                    break;
                case JTokenType.None:
                    value = null;
                    break;
                case JTokenType.Null:
                    value = null;
                    break;
                case JTokenType.Object:
                    value = GetJSONObject((JObject)token); 
                    break;
                case JTokenType.Property:
                    value = token.ToString(); 
                    break;
                case JTokenType.Raw:
                    value = token.ToString(); 
                    break;
                case JTokenType.String:
                    value = s;
                    break;
                case JTokenType.Undefined:
                    value = token.ToString();
                    break;
            }

            return value;
        }

        private string GetMD5Hash(string value) 
        {            
            var hashAlgorithm = System.Security.Cryptography.HashAlgorithm.Create("MD5");
            
            byte[] hashValue = hashAlgorithm.ComputeHash(new UTF8Encoding().GetBytes(value));

            //Bit convertor return the byte to string as all caps hex values seperated by "-"         
            return BitConverter.ToString(hashValue).Replace("-", "").ToLowerInvariant();     
        }

        private bool IsNozbeError(string result, JObject o)
        {
            _lastResult = result;

            try
            {
                _lastErrors = GetJSONArray((JArray)o["error"]);
            }
            catch 
            {
                _lastErrors = new System.Collections.ArrayList();
            }

            
            if (_lastErrors == null || _lastErrors.Count == 0)
            {                
                return false;
            }
            else
            {
                return true;
            }
        }

        /// <summary>
        /// Returns the data (as a string) returned by a GET of the URL.
        /// </summary>
        /// <param name="url">
        /// The url to GET.
        /// </param>
        /// <returns>
        /// The data returned by GETing the URL.
        /// </returns>
        public static string GetURL(string url)
        {
            using (var client = new WebClient())
            {
                var response = client.DownloadString(url);

                return response;
            }
        }

        /// <summary>
        /// Returns the data (as a string) returned by a POST of the URL.
        /// </summary>
        /// <param name="url">
        /// The url to POST.
        /// </param>
        /// <param name="json">
        /// The string of data to POST to the URL.  Presumably the JSON formatted data expected by Nozbe.
        /// </param>
        /// <returns>
        /// The data returned by POSTing the JSON data to the URL.
        /// </returns>
        public static string PostURL(string url, string json)
        {
            var bytes = System.Web.HttpUtility.UrlEncodeToBytes(json);

            using (var client = new WebClient())
            {
                client.Headers.Add("Content-Type", "application/json");
                var response = client.UploadData(url, "POST", bytes);

                // return Encoding.Default.GetString(response);
                return Encoding.UTF8.GetString(response);
            }
        }
    }
}
